﻿using System;
using System.Collections.Generic;
using ParserGenerator.CharacterMapping;
using ParserGenerator.Exceptions;
using ParserGenerator.Extensions;
using ParserGenerator.SyntacticAnalysis;

namespace ParserGenerator.GrammarAnalysis
{
    [Serializable]
    internal sealed class CharClassCollector : ICharClassCollector
    {
        private readonly LexicalAliasTable _aliases;
        private readonly HashSet<LexicalAlias> _collected;
        private readonly CharMapBuilder _charMap;
        public CharMapBuilder CharMap => _charMap;

        public CharClassCollector(LexicalAliasTable table)
            : this(table, CharInterval.Universal)
        {
        }

        public CharClassCollector(LexicalAliasTable aliases, CharInterval universal)
        {
            if (aliases == null)
                throw new ArgumentNullException(nameof(aliases));
            _aliases = aliases;
            _collected = new HashSet<LexicalAlias>();
            _charMap = new CharMapBuilder(universal);
        }

        public void ProcessStringLiteral(string value)
        {
            if (value == null)
                throw new ArgumentNullException(nameof(value));
            foreach (int c in value.EnumCodepoints())
            {
                CharClassBuilder ccb = new CharClassBuilder(_charMap.Universal);
                ccb.Add(c);
                CharClass cc = ccb.Build();
                _charMap.Intern(cc);
            }
        }

        public void ProcessLexicalExpression(LexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            n.AcceptCharClassCollector(this);
        }

        public void ProcessEmptyLexicalExpression(EmptyLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
        }

        public void ProcessStringLexicalExpression(StringLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            string value = n.Value.Value;
            ProcessStringLiteral(value);
        }

        public void ProcessQuoteStringLexicalExpression(QuoteStringLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            string value = n.Value.Value;
            ProcessStringLiteral(value);
        }

        public void ProcessCharClassLexicalExpression(CharClassLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            CharClass cc = n.Value.Evaluate(_charMap.Universal);
            _charMap.Intern(cc);
        }

        public void ProcessNameReferenceLexicalExpression(NameReferenceLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            LexicalAlias alias = _aliases.Lookup(n.Name.Name, n.Context);
            if (_collected.Add(alias))
                ProcessLexicalExpression(alias.Pattern);
        }

        public void ProcessKleeneLexicalExpression(KleeneLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            ProcessLexicalExpression(n.Operand);
        }

        public void ProcessOptionalLexicalExpression(OptionalLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            ProcessLexicalExpression(n.Operand);
        }

        public void ProcessPositiveLexicalExpression(PositiveLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            ProcessLexicalExpression(n.Operand);
        }

        public void ProcessRepeatLexicalExpression(RepeatLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            ProcessLexicalExpression(n.Operand);
        }

        public void ProcessConcatLexicalExpression(ConcatLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            ProcessLexicalExpression(n.Left);
            ProcessLexicalExpression(n.Right);
        }

        public void ProcessUnionLexicalExpression(UnionLexicalExpression n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            ProcessLexicalExpression(n.Left);
            ProcessLexicalExpression(n.Right);
        }

        public void ProcessLexicalAliasDeclarator(LexicalAliasDeclarator n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            ProcessLexicalExpression(n.Pattern);
        }

        public void ProcessLexicalAliases()
        {
            foreach (var t in _aliases.Aliases)
                if (_collected.Add(t.Value))
                    ProcessLexicalExpression(t.Value.Pattern);
        }

        public void ProcessLexicalRuleDeclarator(LexicalRuleDeclarator n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            ProcessLexicalExpression(n.Pattern);
        }

        public void ProcessLexicalStartDeclarator(LexicalStartDeclarator n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            foreach (LexicalRuleDeclarator rule in n.Rules)
                ProcessLexicalRuleDeclarator(rule);
        }

        public void ProcessLexicalDeclarator(LexicalDeclarator n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (n.IsAlias)
                ProcessLexicalAliasDeclarator(n.AsAlias.Declarator);
            else
                ProcessLexicalStartDeclarator(n.AsStart.Declarator);
        }

        public void ProcessLexicalDeclaration(LexicalDeclaration n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            foreach (LexicalDeclarator item in n.Declarators)
                ProcessLexicalDeclarator(item);
        }
    }
}

namespace ParserGenerator.SyntacticAnalysis
{
    partial class CharClassDescriptor
    {
        public CharClass Evaluate(CharInterval universal)
        {
            CharClassBuilder builder = new CharClassBuilder(universal);
            foreach (CharIntervalDescriptor n in Intervals)
            {
                CharInterval item = CharInterval.New(n.First.Value, n.Last.Value);
                if (!universal.Includes(item))
                {
                    string message = string.Format(SR.CharMustBeInRange, universal);
                    GrammarErrorException e = new GrammarErrorException(message);
                    e.AddLocation(n.Context);
                    throw e;
                }
                builder.Add(item);
            }
            CharClass value = builder.Build(IsNegated);
            return value;
        }
    }
}
